/**
 * MONITOR:
 * 	- Definicion: 
 * 		Mecanismo de abstraccion de datos que agrupa o encapsula representaciones de recursos
 * 		abstractos y proporciona un conjunto de operaciones  que son las unicas que se pueden
 * 		realizar sobre dichos recursos.
 * 
 * 	- Caracteristicas:
 * 		Contiene variables que representan el estado del recurso.
 * 		Permite el cotrol estructurado de la exclusion mutua.
 * 		Proporciona un mecanismo de control de las condiciones de sincronizacion.
 * 		Introduce encapsulacion de datos.
 * 		Permite la implementacion de tipos de datos abstractos.
 *		Proporciona mecanismos para garantizar el acceso exclusivo a los datos y para implementar condiciones de sincronizacion. 
 *
 *	- Elementos que lo componen:
 *		Conjunto de variables Permaentes (locales al monitor, no pueden ser modificadas por otros procesos).
 *		Codigo de inicializacion (constructor).
 *		Procedimientos accesibles por parte de los procesos externos.
 *		Conjunto de procedimientos internos para manipulacion de las variables permanentes.
 * 
 * 
 * @author Marcelo
 * @version 1.1
 * @created 05-nov-2013 09:08:28 a.m.
 */

package Baterias;

import java.util.concurrent.locks.*;

import Misil.MisilAliado;
import Utilidades.Logger;

public class Monitor {
	// Campos
	private final ReentrantLock lock = new ReentrantLock();
	private Condition[] condiciones;// bateriaNorte,bateriaSur,bateriaEste,bateriaOeste;
	private Petri petri;

	/**
	 * @brief: Constructor predeterminado del monitor.
	 */
	public Monitor() {
		condiciones = new Condition[4];
		petri = new Petri(); // petri.printMatriz();
		
		// Condiciones de las colas de condicion
		condiciones[0] = lock.newCondition(); // BateriaNorte
		condiciones[1] = lock.newCondition(); // BateriaSur
		condiciones[2] = lock.newCondition(); // BateriaEste
		condiciones[3] = lock.newCondition(); // BateriaOeste
	}

	/**
	 * @brief Funcion que dispara una transicion en la red de petri para
	 *        verificar si es posible dsparar determinada bateria. Si no es
	 *        posible, se pone a dormir al hilo que solicito el disparo, hasta
	 *        que la bateria seleccionada ejecute un resume.
	 * @param misil que se desea disparar.
	 */
	public void solicitarBateria(MisilAliado misil) {
		try {
			lock.lock();
			//Prints
			System.out.println("Disparar transicion para " + misil.getID());
			Logger.escribir("Disparar transicion para " + misil.getID());

			//Obtener el vector de disparo
			int indice = misil.getIndiceBateria();
			int[][] vectorDeDisparo = petri.getVectorDeDisparo(indice);

			/*
			 * Hay que volver a checkear el disparo de la red de petri. Si La
			 * primera vez no funciono, entonces no se actualizo la red, ni
			 * tampoco se dsparo el misil. Por lo tanto, hay que volver a
			 * checkear la condicion disparo hasta que sea posible disparar.
			 */
			if (!petri.dispararTransicion(vectorDeDisparo)) {
				System.out.println(misil.getID()
						+ " Esperando para ser Disparado");
				Logger.escribir(misil.getID() + " Esperando para ser Disparado");
				delay(condiciones[indice]);
				System.err.println(misil.getID()
						+ " Despertado por una operacion RESUME");
				Logger.escribir(misil.getID()
						+ " Despertado por una operacion RESUME");
				petri.dispararTransicion(vectorDeDisparo);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	/**
	 * @brief Funcion que ejecuta el disparo correspondiente en la red de petri
	 *        para Habilitar la bateria que se pasa como parametro. Tambien
	 *        ejecuta el resume correspondiente.
	 * @param bateria
	 */
	public void habilitarBateria(Bateria bateria) {
		try {
			lock.lock();
			int i = bateria.getIndice();
			// Restart
			int[][] vector = petri.getVectorRestart(i);
			boolean exito = petri.dispararTransicion(vector);

			if (!exito) {
				System.err.println("ERROR AL HABILITAR BATERIA[" + i + "]");
				Logger.escribir("ERROR AL HABILITAR BATERIA[" + i + "]");
				petri.printMarcacion();
				petri.printVector(vector);
			} else {
				// Ejecuto el resume para desbloquear a un misil que este
				// esperando por esta bateria
				resume(condiciones[bateria.getIndice()]);
				System.out.println("Bateria[" + i + "] Habilitada");
				Logger.escribir("Bateria[" + i + "] Habilitada");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}

	/**
	 * @brief Metodo que verifica que determinada bateria este libre
	 * @return
	 */
	public boolean[] getDisponibilidad() {
		boolean disponibles[];
		lock.lock();
		disponibles = petri.checkDisponibles();
		lock.unlock();
		return disponibles;
	}

	/**
	 * @brief Operacion que Bloquea al hilo actual, a espera de la condicion que
	 *        se pasa como parametro. Solo puede llamarse desde una seccion critica.
	 * @param condicion
	 */
	private void delay(Condition condicion) {
		try {
			System.out
					.println("Hilo: " + Thread.currentThread() + " Esperando");
			// condicion.await();
			condicion.awaitUninterruptibly();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("Error en operacion Delay()");
		}
	}

	/**
	 * @brief Operacion que despierta un hilo de la cola de la condicion que se
	 *        pasa como parametro. Solo puede llamarse desde una seccion critica.
	 * @param condicion
	 */
	private void resume(Condition condicion) {
		try {
			condicion.signal();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
